﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace Core
{
    public class TcpConnecterSystem : EntitySystem, IAwake, IUpdate, ILateUpdate, IEndUpdate
    {
        internal Dictionary<uint, TcpSession> id2Sessions = new Dictionary<uint, TcpSession>();
        internal Dictionary<IPEndPoint, Connecter> ip2Connecters = new Dictionary<IPEndPoint, Connecter>();

        public void Awake()
        {
            Subscribe<AcceptTcpConnect>(AcceptTcpConnect);
            Subscribe<ConnectTcp>(ConnectTcp);
            Subscribe<DisconnectTcp>(DisconnectTcp);

            Subscribe<GetSession>(GetSession);
            Subscribe<AddSession>(AddSession);
            Subscribe<RemoveSession>(RemoveSession);
            Subscribe<RegisterSession>(RegisterSession);
        }

        public void GetSession(ref GetSession eventData)
        {
            if (id2Sessions.TryGetValue(eventData.sessionID, out var session))
                eventData.session = session;
        }

        private void AddSession(ref AddSession eventData)
        {
            if (!ip2Connecters.TryGetValue(eventData.iPEndPoint, out var connecter))
            {
                new CoreTip(TipLevel.Error, $"找不到连接:{eventData.iPEndPoint}").Publish();
                return;
            }

            if (id2Sessions.TryGetValue(eventData.sessionID, out var session))
            {
                eventData.session = session;
                return;
            }

            session = new TcpSession(eventData.sessionID, eventData.iPEndPoint, NewEntity(), true);
            if (!CheckAndAddSession(eventData.sessionID, session))
                return;

            eventData.session = session;

            new CoreTip($"添加会话：ip:{eventData.iPEndPoint}, id:{eventData.sessionID}").Publish();

            connecter.delayActions.Enqueue(() =>
            {
                connecter.sessions.Add(session.SessionID, session);
            });
        }

        /// <summary>
        /// 移除初始会话，替换默认ID为注册ID
        /// </summary>
        /// <param name="eventData"></param>
        private void RegisterSession(ref RegisterSession eventData)
        {
            eventData.isSuccess = false;

            if (!ip2Connecters.TryGetValue(eventData.iPEndPoint, out var connecter))
            {
                new CoreTip(TipLevel.Error, $"找不到连接:{eventData.iPEndPoint}").Publish();
                return;
            }

            if (connecter.connectSession.SessionID != 0)
            {
                new CoreTip(TipLevel.Error, $"该会话(ID:{connecter.connectSession.SessionID})已经注册过，无法重复注册");
                return;
            }

            var sessionID = eventData.sessionID;
            if (!CheckAndAddSession(sessionID, connecter.connectSession))
                return;

            new CoreTip($"登记会话：ip:{eventData.iPEndPoint}, id:{sessionID}").Publish();
            eventData.isSuccess = true;

            connecter.delayActions.Enqueue(() =>
            {
                // 只有初始连接会话才能登记，其初始sessionID是0
                connecter.sessions.Remove(0);
                connecter.sessions.Add(sessionID, connecter.connectSession);
            });
        }

        private bool CheckAndAddSession(uint sessionId, TcpSession session)
        {
            // 判断当前是否已经存在该ID会话
            if (id2Sessions.TryGetValue(sessionId, out var existSession))
            {
                new CoreTip($"会话在其他地方登录: id:{sessionId}, exist:{existSession.IPEndPoint}, new:{session.IPEndPoint}").Publish();
                new OnAddExistSession(session).Publish();
                return false;
            }

            session.SessionID = sessionId;
            id2Sessions.Add(sessionId, session);
            return true;
        }

        private void RemoveSession(ref RemoveSession eventData)
        {
            if (!ip2Connecters.TryGetValue(eventData.iPEndPoint, out var connecter))
            {
                new CoreTip(TipLevel.Error, $"找不到连接:{eventData.iPEndPoint}").Publish();
                return;
            }

            if (!id2Sessions.TryGetValue(eventData.sessionID, out var session))
                return;

            connecter.delayActions.Enqueue(() =>
            {
                new CoreTip($"移除会话：id:{session.SessionID}").Publish();

                id2Sessions.Remove(session.SessionID);
                connecter.sessions.Remove(session.SessionID);
                session.Dispose();
            });
        }

        private void CheckSendMessage(Connecter connecter)
        {
            if (!connecter.IsValid())
                return;

            if (connecter.isSending)
                return;

            // 如果没有消息了，填充了数据还没发送，需要发送数据
            foreach (var session in connecter.sessions.Values)
            {
                while(session.requestMessages.TryPeek(out var peekMessage))
                {
                    // 判断发送的buff是否能够容纳这个消息
                    if (connecter.requestBuffer.Remain > peekMessage.messageLength)
                    {
                        // 填入数据
                        if (session.requestMessages.TryDequeue(out var message))
                        {
                            Array.Copy(message.buffer, 0, connecter.requestBuffer.data, connecter.requestBuffer.dataLength, message.messageLength);
                            connecter.requestBuffer.dataLength += message.messageLength;
                            MessageBuffer.Push(message);
                        }
                    }
                    else
                    {
                        // 不够装了，直接发送
                        SendAsync(connecter);
                        return;
                    }
                }
            }

            if (connecter.requestBuffer.dataLength > 0)
                SendAsync(connecter);
        }

        private void SendAsync(Connecter connecter)
        {
            connecter.isSending = true;
            connecter.requestArgs.SetBuffer(connecter.requestBuffer.data, 0, connecter.requestBuffer.dataLength);
            if (connecter.socket.SendAsync(connecter.requestArgs))
                return;

            OnSendComplete(connecter);
        }

        private void OnSendComplete(Connecter connecter)
        {
            connecter.isSending = false;
            connecter.requestBuffer.Clear();
            if (connecter.requestArgs.SocketError != SocketError.Success)
            {
                new CoreTip(TipLevel.Error, $"发送失败:{connecter.requestArgs.SocketError}").Publish();
                DisconnectAsync(connecter);
                return;
            }
        }

        internal void AddConnecter(Connecter connecter)
        {
            var connecters = ip2Connecters;
            if (connecters.ContainsKey(connecter.ipEndPoint))
            {
                new CoreTip($"已存在该连接，无法重复连接。endpoint:{connecter.ipEndPoint}").Publish();
                return;
            }

            connecters[connecter.ipEndPoint] = connecter;
            var session = new TcpSession(0, connecter.ipEndPoint, NewEntity(), false);
            connecter.connectSession = session;
            connecter.sessions.Add(session.SessionID, session);
        }

        internal void DisconnectTcp(ref DisconnectTcp eventData)
        {
            if (!ip2Connecters.TryGetValue(eventData.iPEndPoint, out var connecter))
            {
                new CoreTip(TipLevel.Error, $"找不到连接:{eventData.iPEndPoint}").Publish();
                return;
            }

            DisconnectAsync(connecter);
        }

        public void ConnectTcp(ref ConnectTcp eventData)
        {
            var endPoint = new IPEndPoint(IPAddress.Parse(eventData.address), eventData.port);

            new CoreTip($"尝试连接: {endPoint}").Publish();

            var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);

            var connecter = new Connecter(socket, endPoint);
            AddConnecter(connecter);

            connecter.requestArgs.RemoteEndPoint = connecter.ipEndPoint;

            connecter.receiveArgs.Completed += (sender, args) => OnCompleted(connecter, sender, args);
            connecter.requestArgs.Completed += (sender, args) => OnCompleted(connecter, sender, args);

            if (connecter.socket.ConnectAsync(connecter.requestArgs))
                return;

            OnConnectCompleted(connecter);
        }

        private void OnConnectCompleted(Connecter connecter)
        {
            var args = connecter.requestArgs;
            if (args.SocketError != SocketError.Success)
            {
                new CoreTip(TipLevel.Error, $"链接失败：{args.SocketError}, endpoint:{connecter.ipEndPoint}").Publish();
                new OnConnectTcpServerCompleted(null, false).Publish();
                DisconnectAsync(connecter);
                return;
            }

            connecter.connectState = ConnectState.Connected;

            ReceiveAsync(connecter);

            new CoreTip(TipLevel.Success, $"连接成功：{connecter.ipEndPoint}").Publish();
            new OnConnectTcpServerCompleted(connecter.connectSession, true).Publish();
        }

        private void AcceptTcpConnect(ref AcceptTcpConnect eventData)
        {
            var socket = eventData.acceptSocket;
            var connecter = new Connecter(socket, (IPEndPoint)socket.RemoteEndPoint);
            connecter.connectState = ConnectState.Connected;

            connecter.receiveArgs.Completed += (sender, args) => OnCompleted(connecter, sender, args);
            connecter.requestArgs.Completed += (sender, args) => OnCompleted(connecter, sender, args);

            AddConnecter(connecter);
            ReceiveAsync(connecter);
        }

        private void ReceiveAsync(Connecter connecter)
        {
            connecter.receiveArgs.SetBuffer(
                connecter.receiveBuffer.data,
                connecter.receiveBuffer.dataLength,
                connecter.receiveBuffer.Remain);

            if (connecter.socket.ReceiveAsync(connecter.receiveArgs))
                return;

            OnReceiveCompleted(connecter);
        }

        private void OnReceiveCompleted(Connecter connecter)
        {
            if (!connecter.IsValid())
                return;

            var args = connecter.receiveArgs;

            if (args.BytesTransferred == 0)
            {
                new CoreTip(TipLevel.Log, $"远程端关闭链接").Publish();
                DisconnectAsync(connecter);
                return;
            }

            if (args.SocketError != SocketError.Success)
            {
                new CoreTip(TipLevel.Error, $"接收异常:{args.SocketError}").Publish();
                DisconnectAsync(connecter);
                return;
            }

            if (!HandleReceive(connecter))
            {
                new CoreTip(TipLevel.Error, $"解析接收的数据异常").Publish();
                DisconnectAsync(connecter);
                return;
            }

            ReceiveAsync(connecter);
        }

        /// <summary>
        /// 处理接收到的数据
        /// </summary>
        /// <returns>是否处理成功</returns>
        internal bool HandleReceive(Connecter connecter)
        {
            var buffer = connecter.receiveBuffer;
            buffer.dataLength += connecter.receiveArgs.BytesTransferred;

            while (true)
            {
                if (!connecter.IsValid())
                {
                    new CoreTip(TipLevel.Error, "处理接收的数据时，连接断开").Publish();
                    return false;
                }

                if (buffer.dataLength < Config.MESSAGE_LENGTH_SIZE)
                    break;

                // 解析长度
                if (buffer.dataLength >= Config.MESSAGE_LENGTH_SIZE && buffer.receiveMessageLength <= 0)
                    buffer.receiveMessageLength = BitConverter.ToInt32(buffer.data, 0);

                if (buffer.receiveMessageLength > Config.MAX_MESSAGE_SIZE)
                {
                    new CoreTip(TipLevel.Error, $"消息的长度过大:{buffer.receiveMessageLength}").Publish();
                    return false;
                }

                // 当前消息还未接收完毕
                if (buffer.dataLength < buffer.receiveMessageLength)
                    break;

                // 当前消息接收完毕，开始解析消息
                if (!TranspondOrEnqueueMessage(connecter))
                    return false;

                var remainLength = buffer.dataLength - buffer.receiveMessageLength;
                if (remainLength > 0)
                    Array.Copy(buffer.data, buffer.receiveMessageLength, buffer.data, 0, remainLength);

                buffer.receiveMessageLength = 0;
                buffer.dataLength = remainLength;
            }

            return true;
        }

        internal bool TranspondOrEnqueueMessage(Connecter connecter)
        {
            var buffer = connecter.receiveBuffer;

            var sessionID = BitConverter.ToUInt32(buffer.data, Config.SESSION_ID_POSITION);

            var rpcValue = buffer.data[Config.RPC_ID_POSITION];
            // 是否是转发
            var isTranspond = (rpcValue & Config.RPC_TRANSPOND_MASK) > 0;
            if (isTranspond)
            {
                if (sessionID == 0)
                {
                    new CoreTip(TipLevel.Error, $"默认会话不能转发").Publish();
                    return true;
                }

                if (connecter.connectSession.SessionID == 0)
                {
                    new CoreTip(TipLevel.Error, $"没有设置连接ID，无法转发").Publish();
                    return true;
                }

                if (!connecter.connectSession.linkSessionIDs.Contains(sessionID))
                {
                    new CoreTip(TipLevel.Error, $"会话尚未相互连接{connecter.connectSession.SessionID}<-->{sessionID}，无法转发").Publish();
                    return true;
                }

                if (id2Sessions.TryGetValue(sessionID, out var transpondSession))
                {
                    // 设置标志位为非转发
                    buffer.data[Config.RPC_ID_POSITION] &= byte.MaxValue ^ Config.RPC_TRANSPOND_MASK;

                    // 重定向sessionID
                    var uidBytes = BitConverter.GetBytes(connecter.connectSession.SessionID);
                    Array.Copy(uidBytes, 0, buffer.data, Config.SESSION_ID_POSITION, Config.SESSION_ID_SIZE);

                    var messageBuffer = GetMessageBuffer(buffer);
                    transpondSession.SendMessage(messageBuffer);
                }
                else
                {
                    new CoreTip(TipLevel.Error, $"未找到转发会话SessionID:{sessionID}").Publish();
                }
            }
            else
            {
                if (!connecter.sessions.TryGetValue(sessionID, out var session))
                {
                    new CoreTip(TipLevel.Error, $"找不到会话ID：{sessionID}，可能用户已经断开，不处理消息").Publish();
                    return true;
                }

                var messageBuffer = GetMessageBuffer(buffer);

                // 是否是rpc响应消息
                var isRpcResponse = (rpcValue & Config.RPC_RESPONSE_MASK) > 0;

                // 大于说明是响应
                if (isRpcResponse)
                {
                    session.rpcReceiveMessages.Enqueue(messageBuffer);
                }
                else
                {
                    session.receiveMessages.Enqueue(messageBuffer);
                }
            }

            return true;
        }
        
        private MessageBuffer GetMessageBuffer(Buffer buffer)
        {
            var messageBuffer = MessageBuffer.Pop();
            messageBuffer.messageLength = buffer.receiveMessageLength;
            Array.Copy(buffer.data, 0, messageBuffer.buffer, 0, buffer.receiveMessageLength);
            return messageBuffer;
        }

        private void DisconnectAsync(Connecter connecter)
        {
            if (!connecter.IsValid())
                return;

            connecter.connectState = ConnectState.Disconnecting;
            new CoreTip($"断开连接: 地址:{connecter.ipEndPoint}, id:{connecter.connectSession.SessionID}").Publish();

            if (connecter.socket.DisconnectAsync(connecter.requestArgs))
                return;

            OnDisconnectCompleted(connecter);
        }

        private void OnDisconnectCompleted(Connecter connecter)
        {
            connecter.connectState = ConnectState.Disconnected;
        }

        private void OnCompleted(Connecter connecter, object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    connecter.delayActions.Enqueue(() => OnConnectCompleted(connecter));
                    break;
                case SocketAsyncOperation.Disconnect:
                    connecter.delayActions.Enqueue(() => OnDisconnectCompleted(connecter));
                    break;
                case SocketAsyncOperation.Receive:
                    connecter.delayActions.Enqueue(() => OnReceiveCompleted(connecter));
                    break;
                case SocketAsyncOperation.Send:
                    connecter.delayActions.Enqueue(() => OnSendComplete(connecter));
                    break;
                default:
                    new CoreTip(TipLevel.Error, $"异常Tcp操作: operation:{e.LastOperation}, error:{e.SocketError}").Publish();
                    DisconnectAsync(connecter);
                    break;
            }
        }

        #region 更新操作

        public void Update()
        {
            foreach (var connecter in ip2Connecters.Values)
            {
                foreach (var session in connecter.sessions.Values)
                {
                    while (session.rpcReceiveMessages.TryDequeue(out var messageBuffer))
                    {
                        HandleMessage(session, messageBuffer);
                        MessageBuffer.Push(messageBuffer);
                    }

                    while (!session.IsProcessing)
                    {
                        if (!session.receiveMessages.TryDequeue(out var messageBuffer))
                            break;

                        HandleMessage(session, messageBuffer);
                        MessageBuffer.Push(messageBuffer);
                    }
                }
            }
        }

        internal void HandleMessage(Session session, MessageBuffer messageBuffer)
        {
            var rpcToken = messageBuffer.buffer[Config.RPC_ID_POSITION];
            byte rpcID = (byte)(rpcToken & Config.RPC_ID_MASK);
            var message = new DeserializeMessage(messageBuffer.buffer, messageBuffer.messageLength).Publish().message;

            if (message == null)
            {
                new CoreTip(TipLevel.Error, $"序列化失败，无法处理消息。{session.SessionID}").Publish();
                return;
            }

            if (rpcToken > Config.RPC_ID_MASK)
            {
                // 处理rpc消息
                if (!session.rpcTasks.TryGetValue(rpcID, out var task))
                {
                    new CoreTip(TipLevel.Error, $"RPC回调处理未找到，rpcID:{rpcID},{message.GetType()}").Publish();
                    return;
                }

                //new CoreTip($"处理RPC回调：rpcID:{rpcID}, sessionID:{session.SessionID}, message:{message.GetType()}, time:{DateTime.Now}").Publish();
                session.rpcTasks.Remove(rpcID);
                task.SetResult(message);
            }
            else
            {
                //new CoreTip($"处理消息：rpcID:{rpcID}, sessionID:{session.SessionID}, message:{message.GetType()}, time:{DateTime.Now}").Publish();
                new HandleMessage(session, message, rpcID).Publish();
            }
        }

        public void LateUpdate()
        {
            var removeConnecters = new List<Connecter>();

            foreach (var connecter in ip2Connecters.Values)
            {
                HandlerDelayAction(connecter);

                if (connecter.connectState == ConnectState.Disconnected)
                    removeConnecters.Add(connecter);
            }

            foreach (var connecter in removeConnecters)
            {
                ip2Connecters.Remove(connecter.ipEndPoint);

                foreach (var session in connecter.sessions.Values)
                    id2Sessions.Remove(session.SessionID);

                new OnDisconnectTcpServerCompleted(connecter.connectSession).Publish();
                connecter.Dispose();
            }

            removeConnecters.Clear();
        }

        private void HandlerDelayAction(Connecter connecter)
        {
            // 处理延迟操作
            while (connecter.delayActions.TryDequeue(out var action))
            {
                try
                {
                    action();
                }
                catch (Exception e)
                {
                    new CoreTip(TipLevel.Error, e.ToString()).Publish();
                }
            }
        }

        public void EndUpdate()
        {
            foreach (var connecter in ip2Connecters.Values)
            {
                // 一个角色同时发送多条
                CheckSendMessage(connecter);
            }
        }

        #endregion
    }

    public struct HandleMessage : IEventData
    {
        public ISession session;
        public IMessage message;
        public byte rpcID;

        public HandleMessage(ISession session, IMessage message, byte rpcID)
        {
            this.session = session;
            this.message = message;
            this.rpcID = rpcID;
        }
    }

    public struct ConnectTcp : IEventData
    {
        public string address;
        public int port;

        public ConnectTcp(string address, int port)
        {
            this.address = address;
            this.port = port;
        }
    }

    internal struct DisconnectTcp : IEventData
    {
        public IPEndPoint iPEndPoint;

        public DisconnectTcp(IPEndPoint iPEndPoint)
        {
            this.iPEndPoint = iPEndPoint;
        }
    }

    public struct OnConnectTcpServerCompleted : IEventData
    {
        public ISession session;
        public bool isConnect;

        public OnConnectTcpServerCompleted(ISession session, bool isConnect)
        {
            this.session = session;
            this.isConnect = isConnect;
        }
    }

    public struct OnDisconnectTcpServerCompleted : IEventData
    {
        public ISession session;

        public OnDisconnectTcpServerCompleted(ISession session)
        {
            this.session = session;
        }
    }

    /// <summary>
    /// 作为响应方调用时，应该先已原sessionID回复，再调用次事件注册
    /// </summary>
    public struct RegisterSession : IEventData
    {
        public IPEndPoint iPEndPoint;
        public uint sessionID;

        public RegisterSession(IPEndPoint iPEndPoint, uint sessionID)
        {
            this.iPEndPoint = iPEndPoint;
            this.sessionID = sessionID;
            this.isSuccess = false;
        }

        public bool isSuccess;
    }

    public struct AddSession : IEventData
    {
        public IPEndPoint iPEndPoint;
        public uint sessionID;

        public AddSession(IPEndPoint iPEndPoint, uint sessionID)
        {
            this.iPEndPoint = iPEndPoint;
            this.sessionID = sessionID;

            this.session = null;
        }

        public ISession session;
    }

    public struct RemoveSession : IEventData
    {
        public IPEndPoint iPEndPoint;
        public uint sessionID;

        public RemoveSession(IPEndPoint iPEndPoint, uint sessionID)
        {
            this.iPEndPoint = iPEndPoint;
            this.sessionID = sessionID;
        }
    }

    public struct GetSession : IEventData
    {
        public uint sessionID;

        public GetSession(uint sessionID)
        {
            this.sessionID = sessionID;

            this.session = null;
        }

        public ISession session;
    }

    public struct OnAddExistSession : IEventData
    {
        public ISession session;

        public OnAddExistSession(ISession session)
        {
            this.session = session;
        }
    }
}
